import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

public class EventServer {
    private final UserDataBase users;
    private final HashMap<String, EventDataBase> eventDataBaseCollects;
    private final HashMap<String, EventCollects> eventCollectsPool;
    private final ServerSocket socket;
    private final String workDirectory;
    private boolean isStartSuccess;

    public EventServer(String workDirectory) {
        this.users = new UserDataBase();
        this.eventDataBaseCollects = new HashMap<>();
        this.eventCollectsPool = new HashMap<>();
        this.workDirectory = workDirectory + "/";
        this.isStartSuccess = false;

        int port = 5678;
        try {
            this.socket = new ServerSocket(port);
            //this.socket.setSoTimeout(10000);
        } catch (IOException e) {
            LogHelper.messageError(this, "constructor", "创建端口号为 " + port + " 的套接字失败！");

            throw new RuntimeException(e);
        }

        try {
            this.users.pullFromFile(this.workDirectory + "users");
            LogHelper.messageInfo(this, "constructor", "载入用户文件成功！");
            this.isStartSuccess = true;
        } catch (IOException e) {
            LogHelper.messageError(this, "constructor", "载入用户文件失败！");
            throw new RuntimeException(e);
        }
        LogHelper.messageInfo(this, "constructor", "EventServer 已启动");
    }

    private EventDataBase getUsableBase(String userClass, String userName) {
        String userInfo = userClass + userName + ".properties";
        EventDataBase userDataBase = this.eventDataBaseCollects.get(userInfo);

        if (userDataBase != null) {
            try {
                userDataBase.pullAllCollectsFromProperties(this.eventCollectsPool);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return userDataBase;
        }

        boolean state;
        userDataBase = new EventDataBase(this.workDirectory, userClass, userName);

        this.eventDataBaseCollects.put(userInfo, userDataBase);

        try {
            userDataBase.pullAllCollectsFromProperties(this.eventCollectsPool);
        } catch (FileNotFoundException e) {
            File file = new File(userInfo);
            try {
                state = file.createNewFile();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

            if (!state) {
                LogHelper.messageError(this, "getUsableBase", "用户配置 <" + userInfo + "> 似乎已存在，但无法正常打开。");
                return null;
            }

            OutputStreamWriter output;
            try {
                output = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
            } catch (FileNotFoundException ex) {
                throw new RuntimeException(ex);
            }
            try {
                output.write("collect1 = " + userClass + "\n" + "collect2 = " + userClass + userName + "\n");
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            LogHelper.messageInfo(this, "getUsableBase", "创建用户配置 <" + userInfo + "> 成功!");

            try {
                output.close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

            try {
                userDataBase.pullAllCollectsFromProperties(this.eventCollectsPool);
            } catch (FileNotFoundException ex) {
                throw new RuntimeException(ex);
            }
        }
        return userDataBase;
    }

    public void adminRegister(String userClass, String userPassword) {
        LogHelper.messageInfo(this, "adminRegister",
                userClass + ",Admin");
        this.users.insertUser(userClass, "Admin", userPassword);
        try {
            this.users.pushToFile();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String userRegister(String userClass, String userName, String userPassword) {
        LogHelper.messageInfo(this, "userRegister",
                userClass + "," + userName);
        if (userName.compareTo("Admin") == 0) {
            LogHelper.messageWarning(this, "userRegister", "尝试注册 Admin！");
            return "Error&普通用户不能注册 Admin\n";
        }
        this.users.insertUser(userClass, userName, userPassword);
        try {
            this.users.pushToFile();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return "Info&成功注册用户 <" + userClass + userName + ">\n";
    }

    public String insert(String userClass, String userName, String userPassword, long endDate, String eventName, String eventContent, boolean isLongTermEvent) {
        LogHelper.messageInfo(this, "insert",
                userClass + "," + userName + "," + endDate + "," + eventName);
        if (!this.isStartSuccess) {
            LogHelper.messageError(this, "insert", "服务器未启动");
            return "Error&服务器未启动\n";
        }

        if (!this.users.verifyUser(userClass, userName, userPassword)) {
            LogHelper.messageError(this, "insert", "用户验证失败");
            return "Error&用户验证失败！\n";
        }

        EventDataBase userDataBase = this.getUsableBase(userClass, userName);

        if (userDataBase != null && userName.compareTo("Admin") != 0) {
            userDataBase.insertEventItem(userClass + userName, endDate, eventName, eventContent, isLongTermEvent);
        } else if (userDataBase != null && userName.compareTo("Admin") == 0) {
            userDataBase.insertEventItem(userClass, endDate, eventName, eventContent, isLongTermEvent);
        }
        return "Info&插入事件 <" + eventName + "> 成功！\n";
    }

    public String remove(String userClass, String userName, String userPassword, long endDate, String eventName) {
        LogHelper.messageInfo(this, "remove",
                userClass + "," + userName + "," + endDate + "," + eventName);
        if (!this.isStartSuccess) {
            LogHelper.messageError(this, "remove", "服务器未启动");
            return "Error&服务器未启动\n";
        }

        if (!this.users.verifyUser(userClass, userName, userPassword)) {
            LogHelper.messageError(this, "remove", "用户验证失败");
            return "Error&用户验证失败！\n";
        }

        EventDataBase userDataBase = this.getUsableBase(userClass, userName);

        if (userDataBase == null) {
            LogHelper.messageError(this, "remove", "用户无可用数据，请联系管理员修复");
            return "Error&用户无可用数据，请联系管理员修复\n";
        }

        userDataBase.deleteEventItem(userClass + userName, endDate, eventName);
        if (userName.compareTo("Admin") == 0) {
            userDataBase.deleteEventItem(userClass, endDate, eventName);
        }
        return "Info&已移除事件 <" + endDate + "," + eventName + ">\n";
    }

    public String query(String userClass, String userName, String userPassword, long since, long until) {
        LogHelper.messageInfo(this, "query",
                userClass + "," + userName + "," + since + "," + until);
        if (!this.isStartSuccess) {
            LogHelper.messageError(this, "query", "服务器未启动");
            return "Error&服务器未启动\n";
        }

        if (!this.users.verifyUser(userClass, userName, userPassword)) {
            LogHelper.messageError(this, "query", "用户验证失败");
            return "Error&用户验证失败！\n";
        }

        EventDataBase userDataBase = this.getUsableBase(userClass, userName);

        if (userDataBase == null) {
            LogHelper.messageError(this, "query", "用户无可用数据，请联系管理员修复");
            return "Error&服务器错误: 未获取到用户数据";
        }

        EventCollects collects = userDataBase.query(since, until);

        StringBuilder response = new StringBuilder();
        response.append("Response&操作成功！\n");
        if (collects == null) {
            return response.toString();
        }

        for (EventItem item : collects.getEventItems()) {
            response.append(item.getEndDate());
            response.append("&");
            response.append(item.getEventName());
            response.append("&");
            response.append(item.getEventContent());
            response.append("&");
            response.append(item.isLongTermEvent());
            response.append("\n");
        }

        return response.toString();
    }

    private Socket getClient() {
        Socket server;
        try {
            server = this.socket.accept();
            LogHelper.messageInfo(this, "getServer", "获取到连接: " + server.getRemoteSocketAddress());
        } catch (IOException e) {
            LogHelper.messageError(this, "getServer", "accept 出错！");
            return null;
        }
        return server;
    }

    private String getClientRequest(Socket server) {
        DataInputStream requestStream;
        String request;

        try {
            requestStream = new DataInputStream(server.getInputStream());
        } catch (SocketTimeoutException e) {
            LogHelper.messageWarning(this, "getServerRequest",
                    "获取请求超时，将关闭一个 Socket");
            return null;
        } catch (IOException e) {
            LogHelper.messageError(this, "getServerRequest", "建立输入流出错！");
            return null;
        }

        byte[] buf = new byte[10240];
        int len;
        try {
            len = requestStream.read(buf);
        } catch (IOException e) {
            LogHelper.messageError(this, "getServerRequest", "读取输入流出错！" + e);
            return null;
        }

        if (len < 13) {
            return null;
        }

        String requestHead = new String(buf, 0, 12);
        if (requestHead.compareTo("EventRequest") != 0) {
            return null;
        }

        request = new String(buf,12, len - 12);
        return request;
    }

    private void sendServerResponse(Socket server, String response) {
        DataOutputStream responseStream;

        if (response.compareTo("null") == 0) {
            return;
        }

        try {
            responseStream = new DataOutputStream(server.getOutputStream());
        } catch (IOException e) {
            LogHelper.messageError(this, "sendServerResponse", "建立输出流出错！");
            return;
        }

        try {
            byte[] buf = response.getBytes(StandardCharsets.UTF_8);
            responseStream.write(buf);
        } catch (IOException e) {
            LogHelper.messageError(this, "getServerRequest", "写输出流出错！");
        }
    }

    private long onlyDateParser(String dateStr) throws NumberFormatException {
        if (dateStr.compareTo("today") == 0) {
            return OnlyDate.getDate();
        }
        if (dateStr.compareTo("future") == 0) {
            return 99991231;
        }
        return Long.parseLong(dateStr);
    }

    private String parserEventRequest(String request) {
        String[] tokens = request.split("&");
        String response;
        long endDate;
        boolean hasError = false;
        switch (tokens[0]) {
            case "query":
                if (tokens.length != 6) {
                    response = "Error&参数错误！\n";
                    hasError = true;
                    break;
                }
                long since;
                long until;
                try {
                    since = this.onlyDateParser(tokens[4]);
                    until = this.onlyDateParser(tokens[5]);
                } catch (NumberFormatException e) {
                    response = "Error&日期解析错误！" + "Date1:<" + tokens[4] + ">,Date2:<" + tokens[5] + ">";
                    hasError = true;
                    break;
                }
                response = this.query(tokens[1], tokens[2], tokens[3], since, until);
                break;
            case "insert":
                if (tokens.length != 8) {
                    response = "Error&参数错误！";
                    hasError = true;
                    break;
                }
                try {
                    endDate = Long.parseLong(tokens[4]);
                } catch (NumberFormatException e) {
                    response = "Error&事件截至日期解析错误！" + e;
                    hasError = true;
                    break;
                }
                response = this.insert(tokens[1], tokens[2], tokens[3], endDate, tokens[5], tokens[6], Boolean.parseBoolean(tokens[7]));
                break;
            case "remove":
                if (tokens.length != 6) {
                    response = "Error&参数错误！";
                    hasError = true;
                    break;
                }
                try {
                    endDate = Long.parseLong(tokens[4]);
                } catch (NumberFormatException e) {
                    response = "Error&事件截至日期解析错误！" + e;
                    hasError = true;
                    break;
                }
                response = this.remove(tokens[1], tokens[2], tokens[3], endDate, tokens[5]);
                break;
            case "register":
                if (tokens.length != 4) {
                    response = "Error&参数错误！";
                    hasError = true;
                    break;
                }
                response = this.userRegister(tokens[1], tokens[2], tokens[3]);
                break;
            case "quit":
                if (tokens.length != 4) {
                    response = "Error&参数错误！";
                    hasError = true;
                    break;
                }
                if (tokens[1].compareTo("Admin") != 0 || tokens[2].compareTo("Admin") != 0) {
                    response = "Error&参数错误！";
                    hasError = true;
                    LogHelper.messageError(this, "run",
                            "非 Admin 试图退出服务器。参数: " + tokens[1] + "," + tokens[2]);
                    break;
                }
                if (!this.users.verifyUser(tokens[1], tokens[2], tokens[3])) {
                    response = "Error&参数错误！";
                    hasError = true;
                    LogHelper.messageError(this, "run",
                            "Admin 退出服务器失败，验证出错！");
                    break;
                }
                return null;
            default:
                LogHelper.messageWarning(this, "run", "拦截到异常请求: " + request);
                response = "null";
                break;
        }

        if (hasError) {
            LogHelper.messageError(this, "run", "请求有误: " + request);
        }

        return response;
    }

    private void socketClose(Socket server) {
        try {
            server.close();
        } catch (IOException e) {
            LogHelper.messageWarning(this, "run", "关闭 socket 连接失败！");
        }
    }

    public void run() {
        while (true) {
            Socket client = this.getClient();

            if (client == null) {
                continue;
            }

            try {
                client.setSoTimeout(500);
            } catch (SocketException e) {
                LogHelper.messageError(this, "run", "设置超时时间时出现错误\n" + e);
                this.socketClose(client);
                continue;
            }

            String request = this.getClientRequest(client);

            if (request == null) {
                LogHelper.messageWarning(this, "run", "拦截到不正常请求，将关闭连接");
                this.socketClose(client);
                continue;
            }

            String response = this.parserEventRequest(request);

            if (response == null) {
                response = "Info&已退出，再见！";
                this.sendServerResponse(client, response);
                this.socketClose(client);
                return;
            }

            this.sendServerResponse(client, response);
            this.socketClose(client);
        }
    }

    public static void main(String[] args) {
        if (args.length == 0) {
            LogHelper.messageError(EventServer.class, "main", "请指定初始文件夹");
            return;
        } else if (args.length > 1) {
            LogHelper.messageError(EventServer.class, "main", "用法: program [初始文件夹]");
            return;
        }
        EventServer server = new EventServer(args[0]);
        server.run();
    }
}
